/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.berry.MedCheck;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

/**
 * This class does all the work for setting up and managing Bluetooth
 * connections with other devices. It has a thread that listens for
 * incoming connections, a thread for connecting with a device, and a
 * thread for performing data transmissions when connected.
 */
public class BluetoothChatService {
	// Debugging
	private static final String TAG = Utils.TAG2;
	private static final boolean D = true;

	// Name for the SDP record when creating server socket
	private static final String NAME_SECURE = "BluetoothChatSecure";
	private static final String NAME_INSECURE = "BluetoothChatInsecure";

	private static final boolean USE_SPP_UUID = true;
	// Unique UUID for this application
	private static final UUID MY_UUID_SECURE =UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
	private static final UUID MY_UUID_INSECURE =UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66");

	//这条是蓝牙串口通用的UUID，不要更改
	private static final UUID MY_UUID_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	// Member fields
	private final BluetoothAdapter mAdapter;
	private final Handler mHandler;
	private AcceptThread mSecureAcceptThread;
	private AcceptThread mInsecureAcceptThread;
	private ConnectThread mConnectThread;
	private ConnectedThread mConnectedThread;
	private int mState;
	private Context mContext;

	// Constants that indicate the current connection state
	public static final int STATE_NONE = 0;       // we're doing nothing
	public static final int STATE_LISTEN = 1;     // now listening for incoming connections
	public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
	public static final int STATE_CONNECTED = 3;  // now connected to a remote device

	/**
	 * Constructor. Prepares a new BluetoothChat session.
	 *
	 * @param context The UI Activity Context
	 * @param handler A Handler to send messages back to the UI Activity
	 */
	public BluetoothChatService(Context context, Handler handler) {
		mAdapter = BluetoothAdapter.getDefaultAdapter();
		mState = STATE_NONE;
		mHandler = handler;
		mContext = context;
	}

	/**
	 * Set the current state of the chat connection
	 *
	 * @param state An integer defining the current connection state
	 */
	private synchronized void setState(int state) {
		if (D) Log.d(TAG, "BluetoothChatService setState() " + mState + " -> " + state);
		mState = state;
		// Give the new state to the Handler so the UI Activity can update
		mHandler.obtainMessage(DataActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	}

	/**
	 * Return the current connection state.
	 */
	public synchronized int getState() {
		return mState;
	}

	/**
	 * Start the chat service. Specifically start AcceptThread to begin a
	 * session in listening (server) mode. Called by the Activity onResume()
	 */
	public synchronized void start() {
		if (D) Log.d(TAG, "BluetoothChatService start");

		// Cancel any thread attempting to make a connection
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		setState(STATE_LISTEN);

		// Start the thread to listen on a BluetoothServerSocket
		if (mSecureAcceptThread == null) {
			mSecureAcceptThread = new AcceptThread(true);
			mSecureAcceptThread.start();
		}
		if (mInsecureAcceptThread == null) {
			mInsecureAcceptThread = new AcceptThread(false);
			mInsecureAcceptThread.start();
		}
	}

	/**
	 * Start the ConnectThread to initiate a connection to a remote device.
	 *
	 * @param device The BluetoothDevice to connect
	 * @param secure Socket Security type - Secure (true) , Insecure (false)
	 */
	public synchronized void connect(BluetoothDevice device, boolean secure) {
		if (D) Log.d(TAG, "BluetoothChatService connect to: " + device);

		// Cancel any thread attempting to make a connection
		if (mState == STATE_CONNECTING) {
			if (mConnectThread != null) {
				mConnectThread.cancel();
				mConnectThread = null;
			}
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// Start the thread to connect with the given device
		mConnectThread = new ConnectThread(device, secure);
		mAdapter.cancelDiscovery();
		mConnectThread.start();
		setState(STATE_CONNECTING);
	}

	/**
	 * Start the ConnectedThread to begin managing a Bluetooth connection
	 *
	 * @param socket The BluetoothSocket on which the connection was made
	 * @param device The BluetoothDevice that has been connected
	 */
	public synchronized void connected(BluetoothSocket socket, BluetoothDevice device, final String socketType) {
		if (D) Log.d(TAG, "connected, Socket Type:" + socketType);

		// Cancel the thread that completed the connection
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// Cancel the accept thread because we only want to connect to one device
		if (mSecureAcceptThread != null) {
			mSecureAcceptThread.cancel();
			mSecureAcceptThread = null;
		}
		if (mInsecureAcceptThread != null) {
			mInsecureAcceptThread.cancel();
			mInsecureAcceptThread = null;
		}

		// Start the thread to manage the connection and perform transmissions
		mConnectedThread = new ConnectedThread(socket, socketType);
		mConnectedThread.start();

		// Send the name of the connected device back to the UI Activity
		Message msg = mHandler.obtainMessage(DataActivity.MESSAGE_DEVICE_NAME);
		Bundle bundle = new Bundle();
		bundle.putString(DataActivity.DEVICE_NAME, device.getName());
		bundle.putString(DataActivity.DEVICE_ADDRES, device.getAddress());
		msg.setData(bundle);
		mHandler.sendMessage(msg);

		setState(STATE_CONNECTED);
	}

	/**
	 * Stop all threads
	 */
	public synchronized void stop() {
		if (D) Log.d(TAG, "BluetoothChatService stop");

		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		if (mSecureAcceptThread != null) {
			mSecureAcceptThread.cancel();
			mSecureAcceptThread = null;
		}

		if (mInsecureAcceptThread != null) {
			mInsecureAcceptThread.cancel();
			mInsecureAcceptThread = null;
		}
		setState(STATE_NONE);
	}

	/**
	 * Write to the ConnectedThread in an unsynchronized manner
	 *
	 * @param out The bytes to write
	 * @see ConnectedThread#write(byte[])
	 */
	public void write(byte[] out) {
		// Create temporary object
		ConnectedThread r;
		// Synchronize a copy of the ConnectedThread
		synchronized (this) {
			if (mState != STATE_CONNECTED) return;
			r = mConnectedThread;
		}
		// Perform the write unsynchronized
		r.write(out);
	}

	/**
	 * Indicate that the connection attempt failed and notify the UI Activity.
	 */
	private void connectionFailed() {
		// Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(DataActivity.MESSAGE_CONNECT_FAIL);
		mHandler.sendMessage(msg);
		// Start the service over to restart listening mode
		BluetoothChatService.this.start();
	}

	/**
	 * Indicate that the connection was lost and notify the UI Activity.
	 */
	private void connectionLost() {
		// Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(DataActivity.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(DataActivity.TOAST,mContext.getString(R.string.connection_lost) );
		msg.setData(bundle);
		mHandler.sendMessage(msg);

		msg = mHandler.obtainMessage(DataActivity.MESSAGE_BT_CONNECT_LOSE);
		mHandler.sendMessage(msg);

		// Start the service over to restart listening mode
		BluetoothChatService.this.start();
	}

	/**
	 * This thread runs while listening for incoming connections. It behaves
	 * like a server-side client. It runs until a connection is accepted
	 * (or until cancelled).
	 */
	private class AcceptThread extends Thread {
		// The local server socket
		private final BluetoothServerSocket mmServerSocket;
		private String mSocketType;

		public AcceptThread(boolean secure) {
			BluetoothServerSocket tmp = null;
			mSocketType = secure ? "Secure" : "Insecure";

			// Create a new listening server socket
			//调用listenUsingRfcommWithServiceRecord(String, UUID)方法来获取BluetoothServerSocket对象，
			// 参数String代表了该服务的名称，UUID代表了和客户端连接的一个标识（128位格式的字符串ID，相当于PIN码），
			// UUID必须双方匹配才可以建立连接。
			try {
				if (USE_SPP_UUID == false) {
					if (secure) {
						tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, MY_UUID_SECURE);
					} else {
						//tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, MY_UUID_INSECURE);
					}
				} else {
					if (secure) {
						tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, MY_UUID_SPP);
					} else {
						//tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, MY_UUID_SPP);
					}
				}
				Log.d(TAG, "AcceptThread created");

			} catch (IOException e) {
				Log.e(TAG, "AcceptThread create fail:" + e.toString());
			}
			mmServerSocket = tmp;
		}

		public void run() {
			Log.d(TAG, "AcceptThread run()");

			setName("AcceptThread" + mSocketType);

			BluetoothSocket socket = null;
			if (mmServerSocket == null) {
				return;
			}
			// Listen to the server socket if we're not connected
			while (mState != STATE_CONNECTED) {
				try {
					// This is a blocking call and will only return on a
					// successful connection or an exception
					//调用accept（）方法来监听可能到来的连接请求，当监听到以后，返回一个连接上的蓝牙套接字BluetoothSocket。
					socket = mmServerSocket.accept();
				} catch (IOException e) {
					Log.e(TAG, "AcceptThread run fail, mmServerSocket.accept fail:"+e.toString());
					break;
				}

				// If a connection was accepted
				if (socket != null) {
					synchronized (BluetoothChatService.this) {
						switch (mState) {
							case STATE_LISTEN:
							case STATE_CONNECTING:
								// Situation normal. Start the connected thread.
								connected(socket, socket.getRemoteDevice(),mSocketType);
								break;
							case STATE_NONE:
							case STATE_CONNECTED:
								// Either not ready or already connected. Terminate new socket.
								try {
									socket.close();
								} catch (IOException e) {
									Log.e(TAG, "AcceptThread Could not close unwanted socket:"+ e.toString());
								}
								break;
						}
					}
				}
			}
			if (D) Log.d(TAG, "AcceptThread end, socket Type: " + mSocketType);

		}

		public void cancel() {
			if (D) Log.d(TAG, "AcceptThread cancel" );
			try {
				if (mmServerSocket != null) {
					mmServerSocket.close();
				}
			} catch (IOException e) {
				Log.e(TAG, "AcceptThread cancel fail:"+e.toString());
			}
		}
	}


	/**
	 * This thread runs while attempting to make an outgoing connection
	 * with a device. It runs straight through; the connection either
	 * succeeds or fails.
	 */
	private class ConnectThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final BluetoothDevice mmDevice;
		private String mSocketType;

		public ConnectThread(BluetoothDevice device, boolean secure) {
			mmDevice = device;
			BluetoothSocket tmp = null;
			mSocketType = secure ? "Secure" : "Insecure";

			// Get a BluetoothSocket for a connection with the
			// given BluetoothDevice
			//为了初始化一个与远端设备的连接，需要先获取代表该设备的一个BluetoothDevice对象。
			// 通过BluetoothDevice对象来获取BluetoothSocket并初始化连接，具体步骤：
			//使用BluetoothDevice对象里的方法createRfcommSocketToServiceRecord(UUID)来获取BluetoothSocket。
			// UUID就是匹配码。
			try {
				if (USE_SPP_UUID == false) {
					if (secure) {
						tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
					} else {
						//tmp = device.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
					}
				} else {
					if (secure) {
						tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SPP);
					} else {
						//tmp = device.createInsecureRfcommSocketToServiceRecord(MY_UUID_SPP);
					}
				}
				Log.d(TAG, "ConnectThread Created");

			} catch (IOException e) {
				Log.e(TAG, "ConnectThread Creat fail:"+e.toString());
			}
			mmSocket = tmp;

		}

		public void run() {
			setName("ConnectThread" + mSocketType);
			// Always cancel discovery because it will slow down a connection
			mAdapter.cancelDiscovery();
			// Make a connection to the BluetoothSocket
			try {
				// This is a blocking call and will only return on a
				// successful connection or an exception
				//调用connect（）方法来。如果远端设备接收了该连接，他们将在通信过程中共享RFFCOMM信道，并且connect（）方法返回。
				mmSocket.connect();
				Log.d(TAG, "ConnectThread mmSocket.connect()");
			} catch (IOException e) {
				// Close the socket
				Log.e(TAG, "ConnectThread mmSocket.connect() Fail:"+e.toString());
				try {
					mmSocket.close();
					Log.e(TAG, "ConnectThread  mmSocket.connect() fail:" + e.toString());
				} catch (IOException e2) {
					Log.e(TAG, "unable to close() " + mSocketType +" socket during connection failure:"+e2.toString());
				}
				connectionFailed();
				return;
			}

			// Reset the ConnectThread because we're done
			synchronized (BluetoothChatService.this) {
				mConnectThread = null;
			}

			// Start the connected thread
			connected(mmSocket, mmDevice, mSocketType);
		}

		public void cancel() {
			try {
				mmSocket.close();
				Log.d(TAG, "ConnectThread cancel mmSocket.close()");
			} catch (IOException e) {
				Log.e(TAG, "close() of connect " + mSocketType + " socket failed:"+e.toString());
			}
		}
	}

	/**
	 * This thread runs during a connection with a remote device.
	 * It handles all incoming and outgoing transmissions.
	 */
	private class ConnectedThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final InputStream mmInStream;
		private final OutputStream mmOutStream;

		public ConnectedThread(BluetoothSocket socket, String socketType) {
			mmSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			// Get the BluetoothSocket input and output streams
			//通过调用getInputStream()和getOutputStream()方法来获取输入输出流。然后通过调用read(byte[]) 和write(byte[]).方法来读取或者写数据。
			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
				Log.d(TAG, "ConnectedThread create ,IO ready");
			} catch (IOException e) {
				Log.e(TAG, "ConnectedThread create fail, IO fail:"+e.toString());
			}

			mmInStream = tmpIn;
			mmOutStream = tmpOut;
//			String message = "Hello.--" + Utils.getFullDate(System.currentTimeMillis());
//			byte[] msgBuffer = message.getBytes();
//			try {
//				mmOutStream.write(msgBuffer);
//				Log.d(TAG, "ConnectedThread send Hello message Done" );
//			} catch (IOException e) {
//				Log.e(TAG, "ConnectedThread send Hello message fail");
//			}
		}

		public void run() {
			Log.d(TAG, "ConnectedThread run");
//			byte[] buffer = new byte[512];
			int bytes;

			// Keep listening to the InputStream while connected
			while (true) {
				try {
					// Read from the InputStream
					byte[] buffer = new byte[100];
					bytes = mmInStream.read(buffer);
					mHandler.obtainMessage(DataActivity.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
				} catch (IOException e) {
					Log.e(TAG, "ConnectedThread run fail. mmInStream.read fail:"+e.toString());
					connectionLost();
					// Start the service over to restart listening mode
					BluetoothChatService.this.start();
					break;
				}
			}
		}

		/**
		 * Write to the connected OutStream.
		 *
		 * @param buffer The bytes to write
		 */
		public void write(byte[] buffer) {
			try {
				mmOutStream.write(buffer);

				// Share the sent message back to the UI Activity
				mHandler.obtainMessage(DataActivity.MESSAGE_WRITE, -1, -1, buffer)
						.sendToTarget();
			} catch (IOException e) {
				Log.e(TAG, "Exception during write:"+e.toString());
			}
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
				Log.e(TAG, "close() of connect socket failed:" + e.toString());
			}
		}
	}
}
